export class Realm {
  readonly global = new Map<string, string>();
  readonly Object = new Map<string, string>();
  readonly Object_Prototype = new Map<string, string>();

  
}

export class EnvironmentRecord {
  private thisValue: any;
  private variables = new Map<string, JSValue>();

  constructor(protected outer: EnvironmentRecord | null = null) {

  }

  get(name: string): JSValue {
    if (this.variables.has(name)) {
      return this.variables.get(name) ?? new JSUndefined();
    }
    if (this.outer != null) {
      return this.outer.get(name);
    }
    return new JSUndefined();
  }

  set(name: string, value: JSValue = new JSUndefined()) {
    if (this.variables.has(name)) {
      this.variables.set(name, value);
    } else if (this.outer != null) {
      this.outer.set(name, value);
    } else {
      this.variables.set(name, value);
    }
  }
}

export class ObjectEnvironmentRecord extends EnvironmentRecord {
  constructor(private object: JSObject, protected outer: EnvironmentRecord | null = null) {
    super(outer);
  }

  get(name: string): JSValue {
    return this.object.get(name);
  }

  set(name: string, value: JSValue = new JSUndefined()) {
    return this.object.set(name, value);
  }
}


export class ExecutionContext {
  constructor(
    readonly realm: Realm,
    // 词法环境
    readonly lexicalEnvironment: EnvironmentRecord,
    // 变量环境
    readonly variableEnvironment = lexicalEnvironment,
  ) {
    
  }
}

export class Reference {
  constructor(private object: EnvironmentRecord, private property: string) {

  }

  set(value: any) {
    this.object.set(this.property, value);
  }

  get() {
    return this.object.get(this.property);
  }
}

export abstract class JSValue {
  abstract get type(): string;
}

export class JSNumber extends JSValue {
  private memory = new ArrayBuffer(8);

  get type() {
    return 'number';
  }

  constructor(value: number = 0) {
    super();
    new Float64Array(this.memory)[0] = value;
  }

  get value(): number {
    return new Float64Array(this.memory)[0];
  }
  
  toString() {

  }

  toNumber() {
    return this;
  }

  toBoolean() {
    if (new Float64Array(this.memory)[0] === 0) {
      return new JSBoolean(false);
    }
    return new JSBoolean(true);
  }
}

export class JSString extends JSValue {
  // private memory: ArrayBuffer;

  get type() {
    return 'string';
  }

  constructor(private characters: string[]) {
    super();
    // this.memory = new ArrayBuffer(characters.length * 2);
  }

  toString() {
    return this;
  }

  toNumber() {
    
  }

  toBoolean() {
  }
}

export class JSBoolean extends JSValue {
  constructor(readonly value = false) {
    super();
  }

  get type() {
    return 'boolean';
  }

  toString() {
    if (this.value) {
      return new JSString(['t', 'r', 'u', 'e']);
    }

    return new JSString(['f', 'a', 'l', 's', 'e']);
  }

  toNumber() {
    if (this.value) {
      return new JSNumber(1);
    }

    return new JSNumber(0);
  }

  toBoolean() {
    return this;
  }
}

export class JSObject extends JSValue {
  private properties = new Map<string, any>();

  constructor(private prototype = null) {
    super();
  }

  get type() {
    return 'object';
  }

  setPrototype(proto: any) {
    this.prototype = proto;
  }

  getPrototype() {
    return this.prototype;
  }

  setProperty(name: string, attributes: any) {
    this.properties.set(name, attributes);
  }

  getProperty(name: string) {
    return this.properties.get(name);
  }

  set(name: string, value: any) {
    this.setProperty(name, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true,
    });
  }

  get(name: string) {
    return this.getProperty(name).value;
  }
}

export class JSNull extends JSValue {
  get type() {
    return 'null';
  }

  toString() {
    return new JSString(['n', 'u', 'l', 'l']);
  }

  toNumber() {
    return new JSNumber(0);
  }

  toBoolean() {
    return new Boolean(false);
  }
}

export class JSUndefined extends JSValue {
  get type() {
    return 'undefined';
  }

  toString() {
    return new JSString(['u', 'n', 'd', 'e', 'f', 'i', 'n', 'e', 'd']);
  }

  toNumber() {
    return new JSNumber(Number.NaN);
  }

  toBoolean() {
    return new Boolean(false);
  }
}

export class JSSymbol extends JSValue {
  constructor(private name: string = '') {
    super();
  }

  get type() {
    return 'symbol';
  }
}

export enum CompletionRecordType {
  'Normal' = 'Normal',
  'Break' = 'Break',
  'Continue' = 'Continue',
}

export class CompletionRecord {
  constructor(
    private type: CompletionRecordType = CompletionRecordType.Normal,
    private value: JSValue = new JSUndefined(),
    private target: any = null,
  ) {

  }
}